<?php

namespace CodeIgniter\Commands\Utilities;

use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Config\DotEnv;

/**
 * Command to display the current environment,
 * or set a new one in the `.env` file.
 */
final class Environment extends BaseCommand
{
	/**
	 * The group the command is lumped under
	 * when listing commands.
	 *
	 * @var string
	 */
	protected $group = 'CodeIgniter';

	/**
	 * The Command's name
	 *
	 * @var string
	 */
	protected $name = 'env';

	/**
	 * The Command's short description
	 *
	 * @var string
	 */
	protected $description = 'Retrieves the current environment, or set a new one.';

	/**
	 * The Command's usage
	 *
	 * @var string
	 */
	protected $usage = 'env [<environment>]';

	/**
	 * The Command's arguments
	 *
	 * @var array<string, string>
	 */
	protected $arguments = [
		'environment' => '[Optional] The new environment to set. If none is provided, this will print the current environment.',
	];

	/**
	 * The Command's options
	 *
	 * @var array
	 */
	protected $options = [];

	/**
	 * Allowed values for environment. `testing` is excluded
	 * since spark won't work on it.
	 *
	 * @var array<int, string>
	 */
	private static $knownTypes = [
		'production',
		'development',
	];

	/**
	 * @inheritDoc
	 *
	 * @param array<string, mixed> $params
	 *
	 * @return void
	 */
	public function run(array $params)
	{
		if ($params === [])
		{
			CLI::write(sprintf('Your environment is currently set as %s.', CLI::color($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, 'green')));
			CLI::newLine();

			return;
		}

		$env = strtolower(array_shift($params));

		if ($env === 'testing')
		{
			CLI::error('The "testing" environment is reserved for PHPUnit testing.', 'light_gray', 'red');
			CLI::error('You will not be able to run spark under a "testing" environment.', 'light_gray', 'red');
			CLI::newLine();

			return;
		}

		if (! in_array($env, self::$knownTypes, true))
		{
			CLI::error(sprintf('Invalid environment type "%s". Expected one of "%s".', $env, implode('" and "', self::$knownTypes)), 'light_gray', 'red');
			CLI::newLine();

			return;
		}

		if (! $this->writeNewEnvironmentToEnvFile($env))
		{
			CLI::error('Error in writing new environment to .env file.', 'light_gray', 'red');
			CLI::newLine();

			return;
		}

		// force DotEnv to reload the new environment
		// however we cannot redefine the ENVIRONMENT constant
		putenv('CI_ENVIRONMENT');
		unset($_ENV['CI_ENVIRONMENT'], $_SERVER['CI_ENVIRONMENT']);
		(new DotEnv(ROOTPATH))->load();

		CLI::write(sprintf('Environment is successfully changed to "%s".', $env), 'green');
		CLI::write('The ENVIRONMENT constant will be changed in the next script execution.');
		CLI::newLine();
	}

	/**
	 * @see https://regex101.com/r/4sSORp/1 for the regex in action
	 *
	 * @param string $newEnv
	 *
	 * @return boolean
	 */
	private function writeNewEnvironmentToEnvFile(string $newEnv): bool
	{
		$baseEnv = ROOTPATH . 'env';
		$envFile = ROOTPATH . '.env';

		if (! is_file($envFile))
		{
			if (! is_file($baseEnv))
			{
				CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow');
				CLI::write('It is impossible to write the new environment type.', 'yellow');
				CLI::newLine();

				return false;
			}

			copy($baseEnv, $envFile);
		}

		$pattern = preg_quote($_SERVER['CI_ENVIRONMENT'] ?? ENVIRONMENT, '/');
		$pattern = sprintf('/^[#\s]*CI_ENVIRONMENT[=\s]+%s$/m', $pattern);

		return file_put_contents(
			$envFile,
			preg_replace($pattern, "\nCI_ENVIRONMENT = {$newEnv}", file_get_contents($envFile), -1, $count)
		) !== false && $count > 0;
	}
}
